package com.newmainsoftech.dao.entity.relation;

import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Simple <code>{@link HoldeeIteratorProxy}</code> implementation tightly coupled to 
 * <code>{@link EntityBiLinkCollectionProxyImpl}</code> instance for sake of synchronization with it.<br />
 * 
 * @author Arata Y.
 *
 * @param <R>
 * @param <E>
 * @see EntityBiLinkCollectionProxyImpl
 * @see EntityBiLinkCollectionProxyImplAspect
 */
public class HoldeeIteratorCramptedProxyImpl<R extends HolderSide<E>, E extends HoldeeSide<R>> 
implements HoldeeIteratorProxy {
	private final EntityBiLinkCollectionProxyImpl<R, E> collectionProxyImpl;
		public EntityBiLinkCollectionProxyImpl<R, E> getCollectionProxyImpl() {
			return collectionProxyImpl;
		}
	private final Iterator<E> delegateIterator;
		protected Iterator<E> getDelegateIterator() {
			return delegateIterator;
		}
		
	public HoldeeIteratorCramptedProxyImpl( 
			final EntityBiLinkCollectionProxyImpl<R, E> collectionProxyImpl) {
		if ( collectionProxyImpl == null) {
			throw new IllegalArgumentException(
					String.format( 
							"Value of %1$s argument cannot be null but %2$s instance.",
							"collectionProxyImpl",
							EntityBiLinkCollectionProxyImpl.class.getSimpleName())
					);
		}
		this.collectionProxyImpl = collectionProxyImpl;
		this.delegateIterator = collectionProxyImpl.getHolderEntity().getHoldees().iterator();
	}
	
	private E holdeeEntity;
		protected E getHoldeeEntity() {
			return holdeeEntity;
		}
		protected void setHoldeeEntity( final E holdeeEntity) {
			this.holdeeEntity = holdeeEntity;
		}
		
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object interNext( final Object proxy, final Method method) {
		E holdee = getDelegateIterator().next();
			setHoldeeEntity( holdee);
		return holdee;
	}

	/**
	 * {@inheritDoc}
	 * This will also establish relation between <code>{@link HoldeeSide}</code> entity (what 
	 * is currently  pointed by iterator and being removed from underneath collection) and 
	 * <code>{@link HolderSide}</code> entity (what has been set as default holder entity on 
	 * the <code>{@link EntityBiLinkCollectionProxyImpl}</code> instance given at construction) 
	 * at removal of the <code>{@link HoldeeSide}</code> entity from the underneath collection, if 
	 * null has not been set as the default holder on the <code>{@link EntityBiLinkCollectionProxyImpl}</code> 
	 * instance. As result, that can be said in other words as this moves <code>{@link HoldeeSide}</code> 
	 * entity to holdee collection held by default holder. 
	 * Regarding that matter, please be advised that there can be possibility of ending up to duplicate 
	 * <code>{@link HoldeeSide}</code> entities in the holdee collection held by the default holder entity  
	 * if that holdee collection already contain the identical holdee entity and is not hash base collection.
	 */
	@Override
	public Object interRemove( final Object proxy, final Method method) {
		getDelegateIterator().remove();
		
		final Logger logger = getLogger();
		
		final E holdee = getHoldeeEntity();
		
		if ( !getCollectionProxyImpl().isOrphanRemovalAttributeOn()) {
			final R defaultHolder = getCollectionProxyImpl().getDefaultHolderEntity();
			Collection<E> holdeesWithDefaultHolder = null;
				if ( defaultHolder != null) {
					holdeesWithDefaultHolder = defaultHolder.getHoldees();
				}
			if ( ( holdee != null) && holdee.equals( defaultHolder)) {
				if ( logger.isInfoEnabled()) {
					final String methodName = method.getName();
					logger.info(
							MessageFormat.format(
									"Prevented from setting {0} as holder of self in {1} " 
									+ "operation on {2} collection in {3}.", 
									holdee,
									methodName,
									holdee.getClass().getSimpleName(),
									getCollectionProxyImpl().getHolderEntity())
							);
				}
			}
			else {
				if ( holdeesWithDefaultHolder != null) {
					holdeesWithDefaultHolder.remove( holdee);	
						// to assure no duplicate by different instance 
					holdeesWithDefaultHolder.add( holdee);
					
						if ( logger.isTraceEnabled()) {
							logger.trace( 
									String.format(
											"Set downward link from default holder entity " 
											+ "to holdee entity after cutting downward link " 
											+ "from holder entity to holdee entity. " 
											+ "%nHolder entity: %1$s" 
											+ "%nHoldee eneity: %2$s" 
											+ "%nDefault holder eneity: %3$s",
											getCollectionProxyImpl().getHolderEntity(),
											holdee,
											defaultHolder)
									);
						}
				}
				if ( holdee != null) {
					holdee.setHolderEntity( defaultHolder);
						if ( logger.isTraceEnabled()) {
							logger.trace( 
									String.format(
											"Set upward link from holdee entity to default " 
											+ "holder entity after cutting relation between " 
											+ "holder entity and holdee entity. " 
											+ "%nHolder entity: %1$s" 
											+ "%nHoldee eneity: %2$s" 
											+ "%nDefault holder eneity: %3$s",
											getCollectionProxyImpl().getHolderEntity(),
											holdee,
											defaultHolder)
									);
						}
				}
			}
		}
		else {
			if ( getCollectionProxyImpl().isOkToNullifyHolderAtRemoval()) {
				if ( holdee != null) {
					holdee.setHolderEntity( null);
					
						if ( logger.isTraceEnabled()) {
							logger.trace( 
									String.format(
											"Nullified upward link to holder entity on holdee " 
											+ "entity after cutting relation between " 
											+ "holder entity and holdee entity. " 
											+ "%nHolder entity: %1$s" 
											+ "%nHoldee eneity: %2$s", 
											getCollectionProxyImpl().getHolderEntity(),
											holdee)
									);
						}
				}
			}
			else {
				if ( logger.isDebugEnabled()) {
					logger.debug(
							String.format(
									"Did not cut upward link from holdee entity to holder entity " 
									+ "by assigning null as holder on holdee entity because " 
									+ "orphanRemoval attribute has set to %1$b in %2$s holder " 
									+ "class and isOkToNullifyHolderAtRemoval switch value is %3$b."
									+ "%nHolder eneity: %4$s" 
									+ "%nHoldee entity: %5$s", 
									getCollectionProxyImpl().isOrphanRemovalAttributeOn(),
									getCollectionProxyImpl().getHolderEntity().getClass().getSimpleName(),
									getCollectionProxyImpl().isOkToNullifyHolderAtRemoval(),
									getCollectionProxyImpl().getHolderEntity(),
									holdee)
							);
				}
			}
		}
		
		return null;
	}

	protected Object invokeDelegateIteratorNonExclusiveMethod( final Method method, final Object[] args) 
	throws Throwable {
		return method.invoke( getDelegateIterator(), args);
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
	throws Throwable {
		final Class<?>[] parameterTypes = method.getParameterTypes();
		if ( parameterTypes.length < 1) {
			final String methodName = method.getName();
			switch( methodName) {
			case "next":
				return interNext( proxy, method);
			case "remove":
				return interRemove( proxy, method);
			}
		}
		
		return invokeDelegateIteratorNonExclusiveMethod( method, args);
	}
	
	private Logger logger = LoggerFactory.getLogger( getClass());
		protected Logger getLogger() {
			return logger;
		}
}
